package com.gitee.chessboard.jdbc.executor.mybatis;

import com.gitee.chessboard.jdbc.JdbcManager;
import com.gitee.chessboard.jdbc.parser.*;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import static org.objectweb.asm.Opcodes.*;

/**
 * mybatis mapper接口生成器
 *
 * @author 敲代码的旺财
 * @date 2021/11/25 13:03
 */
public class MybatisMapperBuilder {

    public static Class<?> createMapperClazz(JdbcManager jdbcManager, Object object) {

        String clazzName = "com/gitee/chessboard/jdbc/executor/" + object.getClass().getSimpleName() + "Mapper";
        ClassWriter cw = new ClassWriter(0);
        /*
            v1_8说明类的版本，java1.8
            ACC_XXX 常量是与Java 修饰符对应的标志
            public abstract interface  公开的且无法实例化的接口
            "pkg/Comparable"  是类名，采用全量名
            null是对应的泛型，这里没有
            "java/lang/Object"超类，接口默认继承Object
            null 继承的对象
        */
        cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
                clazzName, null, "java/lang/Object",
                new String[]{"com/gitee/chessboard/jdbc/ExecutorMapper"});
        createTable(jdbcManager, object, cw);
        insert(jdbcManager, object, cw);
        insertOrUpdate(jdbcManager, object, cw);
        getAll(jdbcManager, object, cw);
        delete(jdbcManager, object, cw);
        cw.visitEnd();
        //将这个类提取为字节数组
        byte[] b = cw.toByteArray();
//        FileOutputStream fos = null;
//        try {
//            fos = new FileOutputStream(new File("D://b.class"));
//            fos.write(b);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        ClassLoader classLoader = MybatisMapperBuilder.class.getClassLoader();
        try {
            Method defineClass = ClassLoader.class
                    .getDeclaredMethod("defineClass", String.class, byte[].class,int.class,int.class);
            defineClass.setAccessible(true);
            return (Class<?>) defineClass.invoke(classLoader
                    , clazzName.replace("/", ".")
                    , b
                    ,0
                    ,b.length);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    private static void createTable(JdbcManager jdbcManager, Object object, ClassWriter cw) {
        //解析对象
        Parser parser = new CreateTableParser(jdbcManager);
        String createTableSql = parser.convert(object);
    /*
        定义方法
        ACC_PUBLIC + ACC_ABSTRACT规定这个方法为public、abstract
        "createTable"为方法名
        "(Ljava/lang/String;)V" 描述方法，参数是String，返回值为int
        null是对应的泛型，这里没有
        最后一个参数表示这个方法会抛出的异常
     */
        MethodVisitor mvCreateTable = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "createTable",
                "()V", null, null);
        /*
            方法上添加注解
         */
        AnnotationVisitor vaUpdate = mvCreateTable.visitAnnotation("Lorg/apache/ibatis/annotations/Update;", true);
        AnnotationVisitor vaArray = vaUpdate.visitArray("value");
        vaArray.visit(null, createTableSql);
        vaArray.visitEnd();
        vaUpdate.visitEnd();
    }

    private static void insert(JdbcManager jdbcManager, Object object, ClassWriter cw) {
        //解析对象
        Parser parser = new InsertParser(jdbcManager);
        String insertSql = parser.convert(object);
        String clazzName = object.getClass().getName();
    /*
        定义方法
        ACC_PUBLIC + ACC_ABSTRACT规定这个方法为public、abstract
        "createTable"为方法名
        "(Ljava/lang/String;)V" 描述方法，参数是String，返回值为int
        null是对应的泛型，这里没有
        最后一个参数表示这个方法会抛出的异常
     */
        MethodVisitor mvCreateTable = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "insert",
                "(L" + clazzName.replace(".", "/") + ";)V", null, null);
        /*
            方法上添加注解
         */
        AnnotationVisitor vaUpdate = mvCreateTable.visitAnnotation("Lorg/apache/ibatis/annotations/Insert;", true);
        AnnotationVisitor vaArray = vaUpdate.visitArray("value");
        vaArray.visit(null, insertSql);
        vaArray.visitEnd();
        vaUpdate.visitEnd();
    }

    private static void insertOrUpdate(JdbcManager jdbcManager, Object object, ClassWriter cw) {
        //解析对象
        Parser parser = new InsertOrUpdateParser(jdbcManager);
        String insertOrUpdateSql = parser.convert(object);
        String clazzName = object.getClass().getName();
        MethodVisitor mvCreateTable = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "insertOrUpdate",
                "(L" + clazzName.replace(".", "/") + ";)V", null, null);
        AnnotationVisitor vaUpdate = mvCreateTable.visitAnnotation("Lorg/apache/ibatis/annotations/Insert;", true);
        AnnotationVisitor vaArray = vaUpdate.visitArray("value");
        vaArray.visit(null, insertOrUpdateSql);
        vaArray.visitEnd();
        vaUpdate.visitEnd();
    }

    private static void getAll(JdbcManager jdbcManager, Object object, ClassWriter cw) {
        //解析对象
        Parser parser = new GetAllParser(jdbcManager);
        String clazzName = object.getClass().getName();
        String getAll = parser.convert(object);
        MethodVisitor mv = cw.
                visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getAll", "()Ljava/util/List;"
                        , "()Ljava/util/List<L" + clazzName.replace(".", "/") + ";>;", null);
        AnnotationVisitor avSelect = mv.visitAnnotation("Lorg/apache/ibatis/annotations/Select;", true);
        AnnotationVisitor avArray = avSelect.visitArray("value");
        avArray.visit(null, getAll);
        avArray.visitEnd();
        avSelect.visitEnd();

        AnnotationVisitor avResults = mv.visitAnnotation("Lorg/apache/ibatis/annotations/Results;", true);
        AnnotationVisitor avResultsArray = avResults.visitArray("value");

        ParserHandler parserHandler = jdbcManager.getParserHandler(object);
        String tableId = parserHandler.getTableId();
        for (Map.Entry<String, String> entry : parserHandler.getTableFieldNameAndFieldName().entrySet()) {
            String tableFieldName = entry.getKey();
            String fieldName = entry.getValue();
            AnnotationVisitor avResult = avResultsArray.visitAnnotation(null, "Lorg/apache/ibatis/annotations/Result;");
            avResult.visit("column", tableFieldName);
            avResult.visit("property", fieldName);
            if (tableFieldName.equals(tableId)) {
                avResult.visit("id", Boolean.TRUE);
            }
            avResult.visitEnd();
        }
        avResultsArray.visitEnd();
        avResults.visitEnd();
    }

    /**
     * asm生成mapper类的delete方法
     *
     * @param jdbcManager jdbcManager管理器
     * @param object      待操作对象
     * @param cw
     */
    public static void delete(JdbcManager jdbcManager, Object object, ClassWriter cw) {
        Parser parser = new DeleteParser(jdbcManager);
        String clazzName = object.getClass().getName();
        String delete = parser.convert(object);
        MethodVisitor mv =
                cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "delete", "(L" + clazzName.replace(".", "/") + ";)V", null,
                        null);
        AnnotationVisitor avSelect = mv.visitAnnotation("Lorg/apache/ibatis/annotations/Delete;", true);
        AnnotationVisitor avArray = avSelect.visitArray("value");
        avArray.visit(null, delete);
        avArray.visitEnd();
        avSelect.visitEnd();
    }
}
